<template>
  <div
    class="table-wrapper"
    v-loading="tableLoading"
    element-loading-text="加载中..."
  >
    <el-table
      v-bind="$attrs"
      ref="basicTable"
      style="width: 100%;"
      :data="data"
      :size="size"
      :span-method="spanMethod"
    >
      <el-table-column
        v-if="type === 'checkbox'"
        width="40"
        fixed="left"
      >
        <template #header>
          <el-checkbox :model-value="isSelectedAll" :disabled="isDisabledAll" @change="onSelectAll"></el-checkbox>
        </template>
        <template #default="{row}">
          <el-checkbox
            :model-value="checked.includes(row[selectedDataKey])"
            :disabled="getDisabledRow(row)"
            @change="onCheckboxChange(row)"
          ></el-checkbox>
        </template>
      </el-table-column>
      <el-table-column
        v-if="type === 'radio'"
        label="单选"
        align="center"
        width="50"
        fixed="left"
      >
        <template #default="{row}">
          <el-radio v-model="currentRowId" :label="row[selectedDataKey]"> </el-radio>
        </template>
      </el-table-column>
      <template v-for="item in columns" :key="item.prop">
        <el-table-column
          v-bind="item"
          :width="getColumnWidth(item)"
        >
          <template #default="{row, column, $index}">
            <slot :name="item.prop" :row="row" :column="column">
              <template v-if="item.type === 'img'">
                <el-image
                  v-bind="item.imgProps"
                  v-if="row[item.prop] && row[item.prop] !== '-'"
                  preview-teleported
                  hide-on-click-modal
                  :style="item.style || ''"
                  :src="format({ row, item }, item.format)"
                  :srcset="getSrcSet(row[item.prop], item.getSrcSet)"
                  :preview-src-list="item.preview ? [getSrcSet(row[item.prop], item.getSrcSet)] : []"
                />
                <span v-else>-</span>
              </template>
              <template v-else-if="item.type === 'select'">
                {{ getSelectValue(item, row) }}
              </template>
              <template v-else-if="item.type === 'tag'">
                <el-tag :type="getItemTagType(item, row)">{{ format({ row, item, $index }, item.format) }}</el-tag>
              </template>
              <template v-else-if="item.type === 'tag-select'">
                <el-tag :type="getItemTagType(item, row)">{{ getSelectValue(item, row) }}</el-tag>
              </template>
              <div v-else :class="item.className" :style="item.style">{{ format({ row, item, $index }, item.format) }}</div>
            </slot>
          </template>
        </el-table-column>
      </template>
    </el-table>
    <div class="page-wrapper">
      <paging
        v-if="showPage"
        v-model:current="pageParams.current"
        :total="pageParams.total"
        v-model:size="pageParams.size"
        :layout="pageParams.layout"
        @handleSearch="handleSearch"
      />
    </div>
  </div>
</template>

<script>
import paging from '@/components/BasicTable/paging.vue';
import { computed, defineComponent, ref, watch } from 'vue';

function uniqueArray(array, key) {
  const splitKey = key.split(',');
  const result = [array[0]];
  for (let i = 1; i < array.length; i += 1) {
    const item = array[i];
    let repeat = false;
    for (let j = 0; j < result.length; j += 1) {
      if (splitKey.length === 1 && item[key] === result[j][key]) {
        repeat = true;
        break;
      } else if (splitKey.length === 2) {
        const [key1, key2] = splitKey;
        if (item[key1] === result[j][key1] && item[key2] === result[j][key2]) {
          repeat = true;
          break;
        }
      }
    }
    if (!repeat) {
      result.push(item);
    }
  }
  return result;
}

export default defineComponent({
  components: { paging },
  name: 'BasicTable',
  emits: ['update:selectedData', 'update:selectedObjData', 'handleSelect', 'handleSearch'],
  props: {
    type: String,
    columns: {
      type: Array,
      default: () => ([]),
    },
    disabledRow: Function,
    selectedData: Array,
    selectedDataKey: {
      type: String,
      default: 'id',
    },
    uniqueKey: {
      type: Array,
      default: () => ([]),
    },
    showList: Boolean, // 是否要显示整条数据(json)
    data: {
      type: Array,
      default: () => ([]),
    },
    size: {
      type: String,
      default: 'small',
    },
    pageParams: {
      type: Object,
      default: () => ({
        current: Number,
        size: Number,
        total: Number,
        layout: String,
      }),
    },
    showPage: {
      type: Boolean,
      default: true,
    },
    tableLoading: Boolean,
    mergeOptions: { // 只支持跨行合并
      type: Object,
      default: () => ({
        isMerge: false, // 是否合并单元格
        mergeIndex: [],
        mergeKey: '',
      }),
    },
  },
  setup(props, { emit }) {
    const spanArr = ref([]);
    const position = ref(0);

    // radio
    const currentRowId = computed({
      get() {
        return props.selectedData?.length ? props.selectedData[0] : '';
      },
      set(id) {
        emit('update:selectedData', [id]);
      },
    });

    // checkbox
    const checked = computed({
      get() {
        return props.selectedData?.length ? props.selectedData : [];
      },
      set(ids) {
        emit('update:selectedData', ids);
      },
    });

    // 专门处理数据对象
    watch(
      () => props.selectedData,
      (ids) => {
        if (!props.showList) return;
        if (ids.length === 0) {
          emit('update:selectedObjData', []);
          return;
        }
        // 历史被选中的数据
        const historySelected = props.selectedObjData.filter(select => ids.includes(select[props.selectedDataKey]));
        // 当前页面被选中的对象数组
        const selected = props.data.filter(dd => ids.includes(dd[props.selectedDataKey]));
        const uniqueData = uniqueArray(selected.concat(historySelected), props.uniqueKey || props.selectedDataKey);
        emit('update:selectedObjData', uniqueData);
      },
    );

    // 是否需要全部禁止
    const isDisabledAll = computed({
      get() {
        const selected = [...new Set(props.data.map(select => select[props.selectedDataKey]))];
        // 如果所有data中的selectedDataKey都等于当前分页的disabledData,返回是,否则返回否
        // 当前分页需要被禁止点击的
        const needDisabledData = selected;
        // 当前分页实际被禁止点击的
        const disabledData = [...new Set(props.data.filter(row => getDisabledRow(row)).map(select => select[props.selectedDataKey]))];
        const currentDisabledData = disabledData.filter(select => selected.includes(select));
        return needDisabledData.sort().toString() === currentDisabledData.sort().toString();
      },
    });

    // 是否选中全部
    const isSelectedAll = computed({
      get() {
        const selected = [...new Set(props.data.map(select => select[props.selectedDataKey]))];
        // 当前分页需要被选中的
        const disabledData = [...new Set(props.data.filter(row => getDisabledRow(row)).map(select => select[props.selectedDataKey]))];
        const needSelectedData = selected.filter(select => !disabledData.includes(select));
        // 当前分页实际被选中的
        const currentSelectedData = props.selectedData.filter(select => selected.includes(select));
        return needSelectedData.length && needSelectedData.sort().toString() === currentSelectedData.sort().toString();
      },
    });

    watch(
      () => props.data,
      (list) => {
        // 合并单元格
        if (props.mergeOptions.isMerge) {
          const { mergeKey } = props.mergeOptions;
          spanArr.value = [];
          position.value = 0;
          if (!mergeKey) {
            console.error('必须加入mergeKey属性');
          } else {
            list.forEach((item, index) => {
              if (index === 0) {
                spanArr.value.push(1);
                position.value = 0;
              } else if (item[mergeKey] === list[index - 1][mergeKey]) {
                spanArr.value.push(0);
                spanArr.value[position.value] += 1;
              } else {
                spanArr.value.push(1);
                position.value = index;
              }
            });
          }
        }
      },
      {
        immediate: true,
        deep: true,
      },
    );

    // 勾选了或者取消勾选了checkbox
    const onCheckboxChange = (row) => {
      let selectedData = [...props.selectedData];
      // 取消勾选
      if (props.selectedData.includes(row[props.selectedDataKey])) {
        selectedData = props.selectedData.filter(select => select !== row[props.selectedDataKey]);
        // 勾选
      } else {
        selectedData.push(row[props.selectedDataKey]);
      }
      emit('update:selectedData', selectedData);
    };
    // 全选
    const onSelectAll = (bool) => {
      const selected = props.data.map(select => select[props.selectedDataKey]);
      let selectedData = [];
      // 选中全部
      if (bool) {
        const disabledData = [...new Set(props.data.filter(row => getDisabledRow(row)).map(select => select[props.selectedDataKey]))];
        selectedData = selected.filter(select => !disabledData.includes(select)).concat(props.selectedData);
        // 取消选中全部
      } else {
        selectedData = props.selectedData.filter(select => !selected.includes(select));
      }
      emit('update:selectedData', [...new Set(selectedData)]);
    };

    // 合并单元格
    const spanMethod = ({ columnIndex, rowIndex }) => {
      const { isMerge, mergeIndex } = props.mergeOptions;
      if (isMerge && mergeIndex.includes(columnIndex)) {
        const row = spanArr.value[rowIndex];
        const col = row > 0 ? 1 : 0;
        return {
          rowspan: row,
          colspan: col,
        };
      }
      return [1, 1];
    };
    // 切换单选框
    const changeRadio = (event, row) => {
      emit('update:selectedData', [row[props.selectedDataKey]]);
    };
    const format = ({ row, item, $index }, cb) => {
      if (cb) return cb({ row, item, index: $index });
      if (row[item.prop] || row[item.prop] === 0) return row[item.prop];
      return '-';
    };
    const getSrcSet = (value, cb) => {
      if (cb) return cb(value);
      return value;
    }
    const getColumnWidth = (item) => {
      if (typeof item.width === 'string') return item.width;
      if (typeof item.width === 'number') return String(item.width);
      return 'auto';
    };
    const getSelectValue = (item, row) => {
      const { list = [], prop } = item;
      return list.find(record => record[item.valueKey || 'value'] === row[prop])?.[item.labelKey || 'label'] || '-';
    };
    // 判断当前行是否禁止选择
    const getDisabledRow = (row) => (props.disabledRow ? props.disabledRow(row) : false);
    // 获取标签类型,可动态
    const getItemTagType = (item, row) => {
      if (typeof item.tagType === 'function') {
        return item.tagType(row);
      }
      return item.tagType;
    };
    const handleSearch = () => {
      emit('handleSearch');
    };

    return {
      // data
      spanArr,
      position,
      // computed
      currentRowId,
      checked,
      isDisabledAll,
      isSelectedAll,

      // methods
      handleSearch,
      getItemTagType,
      getSelectValue,
      getColumnWidth,
      spanMethod,
      changeRadio,
      format,
      getSrcSet,
      onSelectAll,
      getDisabledRow,
      onCheckboxChange,
    };
  },
});
</script>

<style>
.table-wrapper .el-radio__label {
  display: none;
}
.table-wrapper .page-wrapper {
  padding: 12px 0;
  display: flex;
  flex-direction: row-reverse;
}
</style>
